package com.atguigu.gmall.search.service.impl;

import com.google.common.collect.Lists;

import com.atguigu.gmall.search.Goods;
import com.atguigu.gmall.search.repo.SearchRepo;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchRespVo;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Description
 * @Return
 * @Author https://weiranyi.github.io
 * @Date Created in 2022-12-12 17:31
 **/
@Service
public class SearchServiceImpl implements SearchService {
    public static final Integer pageSize = 10;
    @Autowired
    SearchRepo searchRepo;
    @Autowired
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public void upGoodsToES(Goods goods) {
        searchRepo.save(goods);
    }

    @Override
    public void downGoodsToES(Long skuId) {
        searchRepo.deleteById(skuId);
    }

    @Override
    public SearchRespVo search(SearchParamVo searchParamVo) {
        // 1.根据前端得请求参数，构建query
        Query query = buildQueryDSL(searchParamVo);
        SearchHits<Goods> result = elasticsearchRestTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));
        // 2.根据检索到的结果，构建前端需要的返回结果
        SearchRespVo respVo = buildSearchResp(result, searchParamVo);
        return respVo;
    }

    @Override
    public void autoIncrementHotScore(Long skuId, Long score) {
        Document document = Document.create();
        document.put("hotScore", score);
        UpdateQuery updateQuery = UpdateQuery
                .builder(skuId + "").withDocAsUpsert(true).withDocument(document).build();
        elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("goods"));
    }

    private Query buildQueryDSL(SearchParamVo searchParamVo) {
        //=============查询开始==============
        // 2.构造一个bool
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        // (1)若1级id不为null，就作为条件
        if (searchParamVo.getCategory1Id() != null) {
            bool.must(QueryBuilders.termQuery("category1Id", searchParamVo.getCategory1Id()));
        }
        // (2)若2级id不为null，就作为条件
        if (searchParamVo.getCategory2Id() != null) {
            bool.must(QueryBuilders.termQuery("category2Id", searchParamVo.getCategory2Id()));
        }
        // (3)若3级id不为null，就作为条件
        if (searchParamVo.getCategory3Id() != null) {
            bool.must(QueryBuilders.termQuery("category3Id", searchParamVo.getCategory3Id()));
        }
        // (4)keyword关键字查询
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            bool.must(QueryBuilders.matchQuery("title", searchParamVo.getKeyword()));
        }
        // (5)品牌不为null，得到tmId
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())) {
            String[] split = searchParamVo.getTrademark().split(":");
            bool.must(QueryBuilders.termQuery("tmId", split[0]));
        }
        // (6)props
        String[] searchParamVoProps = searchParamVo.getProps();
        if (searchParamVoProps != null && searchParamVoProps.length > 0) {
            Arrays.stream(searchParamVoProps)
                    .forEach(item -> {
                        // 4:256GB:机身存储
                        String[] split = item.split(":");
                        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                        boolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                        boolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                        bool.must(QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None));
                    });
        }
        // 1.创建一个原生Query
        NativeSearchQuery query = new NativeSearchQuery(bool);
        //=============查询结束==============
        //=============排序开始==============
        // (7)order //1:asc 1:desc
        String order = searchParamVo.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            Sort.Direction direction = "asc".equals(split[1]) ? Sort.Direction.ASC : Sort.Direction.DESC;
            Sort sort = null;
            switch (split[0]) {
                // 综合
                case "1":
                    sort = Sort.by(direction, "hotScore");
                    break;
                case "2":
                    sort = Sort.by(direction, "price");
                    break;
                default:
                    sort = Sort.by(Sort.Direction.DESC, "hotScore");
            }
            query.addSort(sort);
        }
        //=============排序结束==============
        // (8)pageNo
        //=============分页开始==============
        PageRequest pageRequest = PageRequest.of(searchParamVo.getPageNo() - 1, pageSize);
        query.setPageable(pageRequest);
        //=============分页结束==============
        //==============高亮开始===============
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder()
                    .field("title")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");
            HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
            query.setHighlightQuery(highlightQuery);
        }
        //==============高亮结束===============
        //=============聚合分析开始&品牌============
        TermsAggregationBuilder tmIdAgg = AggregationBuilders
                .terms("tmIdAgg").field("tmId").size(200);
        TermsAggregationBuilder tmNameAgg = AggregationBuilders
                .terms("tmNameAgg").field("tmName").size(1);
        TermsAggregationBuilder tmLogoAgg = AggregationBuilders
                .terms("tmLogoAgg").field("tmLogoUrl").size(1);
        tmIdAgg.subAggregation(tmNameAgg);
        tmIdAgg.subAggregation(tmLogoAgg);
        query.addAggregation(tmIdAgg);
        //=============聚合分析结束&品牌============
        //=============聚合分析开始&属性============
        NestedAggregationBuilder attrAgg = AggregationBuilders
                .nested("attrAgg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders
                .terms("attrIdAgg").field("attrs.attrId").size(200);
        TermsAggregationBuilder attrNameAgg = AggregationBuilders
                .terms("attrNameAgg").field("attrs.attrName").size(1);
        TermsAggregationBuilder attrValueAgg = AggregationBuilders
                .terms("attrValueAgg").field("attrs.attrValue").size(100);
        attrIdAgg.subAggregation(attrNameAgg);
        attrIdAgg.subAggregation(attrValueAgg);
        attrAgg.subAggregation(attrIdAgg);
        query.addAggregation(attrAgg);
        //=============聚合分析结束&属性============
        return query;
    }

    private SearchRespVo buildSearchResp(SearchHits<Goods> result, SearchParamVo searchParamVo) {
        SearchRespVo searchRespVo = new SearchRespVo();
        //1.检索参数
        searchRespVo.setSearchParam(searchParamVo);
        //2.品牌面包屑
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())) {
            searchRespVo.setTrademarkParam(searchParamVo.getTrademark().split(":")[1]);
        }
        //3.属性面包屑
        String[] props = searchParamVo.getProps();
        if (props != null && props.length > 0) {
            //遍历前端传递的所有props参数
            List<SearchRespVo.Props> collect = Arrays.stream(props).map(item -> {
                String[] split = item.split(":");
                SearchRespVo.Props propsInner = new SearchRespVo.Props();
                propsInner.setAttrId(Long.valueOf(split[0]));
                propsInner.setAttrName(split[2]);
                propsInner.setAttrValue(split[1]);
                return propsInner;
            }).collect(Collectors.toList());
            searchRespVo.setPropsParamList(collect);
        }
        //4.品牌列表;  聚合 aggregations
        ParsedLongTerms tmIdAgg = result.getAggregations().get("tmIdAgg");
        List<SearchRespVo.Trademark> tmCollect = tmIdAgg.getBuckets().stream().map((bucket) -> {
            // 品牌id
            long tmId = bucket.getKeyAsNumber().longValue();
            // 例如：华为
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            // 例如：http://10.8.0.1:9000/mall-oss/2022-12-02/89bde8bc-4602-481a-a15d-37fc165fa159-华为.png
            ParsedStringTerms tmLogoAgg = bucket.getAggregations().get("tmLogoAgg");
            String tmLogoUrl = tmLogoAgg.getBuckets().get(0).getKeyAsString();

            SearchRespVo.Trademark trademark = new SearchRespVo.Trademark();
            trademark.setTmId(tmId);
            trademark.setTmName(tmName);
            trademark.setTmLogoUrl(tmLogoUrl);

            return trademark;
        }).collect(Collectors.toList());
        searchRespVo.setTrademarkList(tmCollect);
        //4.属性列表;  聚合 aggregations TODO
        ParsedNested attrAgg = result.getAggregations().get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchRespVo.Attrs> attrsList = attrIdAgg.getBuckets().stream()
                .map((bucket) -> {
                    SearchRespVo.Attrs attrs = new SearchRespVo.Attrs();
                    // 属性id
                    long attrId = bucket.getKeyAsNumber().longValue();
                    attrs.setAttrId(attrId);
                    // 属性name
                    ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                    String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                    attrs.setAttrName(attrName);
                    // 属性值
                    ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                    List<String> valueList = attrValueAgg.getBuckets()
                            .stream()
                            .map((bucketInner -> {
                                return bucketInner.getKeyAsString();
                            })).collect(Collectors.toList());
                    attrs.setAttrValueList(valueList);
                    return attrs;
                }).collect(Collectors.toList());

        searchRespVo.setAttrsList(attrsList);

        //6.UrlParam
        String urlParam = buildUrlParam(searchParamVo);
        searchRespVo.setUrlParam(urlParam);
        //7.MAP
        if (!StringUtils.isEmpty(searchParamVo.getOrder())) {
            SearchRespVo.OrderMap orderMap = new SearchRespVo.OrderMap();
            String[] split = searchParamVo.getOrder().split(":");
            orderMap.setType(split[0]);
            orderMap.setSort(split[1]);
            searchRespVo.setOrderMap(orderMap);
        }
        //8.商品列表
        List<Goods> goodsList = result.getSearchHits().stream().map((item) -> {
            Goods goods = item.getContent();
            if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
                String highlightTitle = item.getHighlightField("title").get(0);
                goods.setTitle(highlightTitle);
            }
            return goods;
        }).collect(Collectors.toList());
        searchRespVo.setGoodsList(goodsList);
        //9.页码
        searchRespVo.setPageNo(searchParamVo.getPageNo());
        //10.总记录数
        long totalHits = result.getTotalHits();
        searchRespVo.setTotalPages(totalHits % pageSize == 0 ? totalHits / pageSize : totalHits / pageSize + 1);
        return searchRespVo;
    }

    private String buildUrlParam(SearchParamVo searchParamVo) {
        StringBuilder builder = new StringBuilder("list.html?");
        // 分类参数
        if (searchParamVo.getCategory1Id() != null) {
            builder.append("&category1Id=" + searchParamVo.getCategory1Id());
        }
        if (searchParamVo.getCategory2Id() != null) {
            builder.append("&category2Id=" + searchParamVo.getCategory2Id());
        }
        if (searchParamVo.getCategory3Id() != null) {
            builder.append("&category3Id=" + searchParamVo.getCategory3Id());
        }
        // keyword
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            builder.append("&keyword=" + searchParamVo.getKeyword());
        }
        // 品牌
        if (!StringUtils.isEmpty(searchParamVo.getTrademark())) {
            builder.append("&trademark=" + searchParamVo.getTrademark());
        }
        // 属性
        if (searchParamVo.getProps() != null && searchParamVo.getProps().length > 0) {
            Arrays.stream(searchParamVo.getProps()).forEach((item) -> {
                builder.append("&props=" + item);
            });
        }
        return builder.toString();
    }
}
